#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>

#include "../comm/log.hpp"
#include "../comm/util.hpp"

namespace ns_runner
{
    using namespace ns_log;
    using namespace ns_util;

    class Runner
    {
    public:
        /*******************************
         * 功能：根据传入的文件名（没有路径、没有后缀），对改文件进行运行
         * 返回值：运行状态
         * < 0：内部函数执行出错
         * == 0：执行成功
         * > 0：用户代码受到异常处理
         ********************************/

        /******************************
         * 拓展：在线OJ是有内存和CPU资源限制的
         *  cpulimit: CPU资源限制的最大时间(单位: s)
         *  memlimit: 内存资源限制的最大时间(单位: KB)
         *******************************/
        static int run(const std::string &filename, int cpulimit, int memlimit)
        {
            /*******************************
             * 运行结果：
             * 1、代码运行成功，结果正确
             * 2、代码运行成功，结果不正确
             * 3、代码因为异常原因，运行失败
             *
             * Runner：并不关心最后代码的运行结果，交付给上层去处理（写到临时文件中）
             * 所以对于run的几个输入输出
             * stdin：不关心
             * stdout："./temp/filename.stdout"文件
             * stderr："./temp/filename.stderr"文件
             *********************************/
            // 1、打开对应的文件(创建)
            umask(0); // 权限掩码设置为000
            int stdin_fd = open(PathSpliceUtil::Stdin(filename).c_str(), O_CREAT | O_RDONLY, 0664);
            int stdout_fd = open(PathSpliceUtil::Stdout(filename).c_str(), O_CREAT | O_WRONLY, 0664);
            int stderr_fd = open(PathSpliceUtil::Stderr(filename).c_str(), O_CREAT | O_WRONLY, 0664);
            if (stdin_fd < 0 | stdout_fd < 0 | stderr_fd < 0)
            {
                LOG(Warning, "打开三个输入输出文件出现错误 in:%d out:%d err:%d", stdin_fd, stdout_fd, stderr_fd);
                return -1;
            }

            // 2、得到可执行文件
            std::string src_exe = PathSpliceUtil::Exe(filename);

            // 3、创建子进程
            pid_t pid = fork();
            if (pid < 0)
            {
                LOG(Warning, "启动可执行文件出错，原因：创建子进程失败");
                close(stdin_fd);
                close(stdout_fd);
                close(stderr_fd); // 关闭文件描述符
                return -2;
            }
            else if (pid == 0)
            {
                // 子进程
                // 3.1.1 子进程重定向三个标准输入输出
                dup2(stdin_fd, STDIN_FILENO);
                dup2(stdout_fd, STDOUT_FILENO);
                dup2(stderr_fd, STDERR_FILENO);
                // 原文件描述符由进程退出自动关闭 fd的生命周期是随进程的

                // 进程替换之前，对进程进行资源的限制
                Runner::SetResourceLimit(cpulimit, memlimit);

                // 3.2.1 程序替换：执行可执行文件。这里OS发信号给进程，进程的重定向失效
                execl(src_exe.c_str(), src_exe.c_str(), nullptr);
                //std::cout << src_exe << std::endl;

                LOG(Warning, "程序[%s]替换失败", src_exe.c_str());
                // 说明程序替换失败
                exit(1); // 子进程退出
            }
            else
            {
                // 父进程
                close(stdin_fd);
                close(stdout_fd);
                close(stderr_fd); // 关闭文件描述符

                // 3.2.1 等待子进程退出
                int status = 0;
                waitpid(pid, &status, 0); // 阻塞等待子进程
                // 得到子进程信息
                //  异常处理： 111 1111
                return status & 0x7F; // 此返回值如果 > 0说明子进程是受到异常信号才退出的！！
            }
        }

    private:
        /***********************
         * 功能：实现对进程的资源限制
         ************************/
        static void SetResourceLimit(int cpulimit, int memlimit)
        {
            // 1. 设置cpu限制资源
            struct rlimit cpu;
            cpu.rlim_cur = cpulimit; //软限制
            cpu.rlim_max = RLIM_INFINITY; //硬限制 软 <= 硬
            setrlimit(RLIMIT_CPU, &cpu);

            //2、设置内存限制资源
            struct rlimit mem;
            //注意：这里RLIMIT_AS设置的时候是按Byte
            mem.rlim_cur = memlimit * 1024; // 转化为字节
            mem.rlim_max = RLIM_INFINITY;
            setrlimit(RLIMIT_AS, &mem);
        }
    };

}